#!/usr/bin/perl -w
use warnings;
use strict;
use Pod::Usage;
use Data::Dumper;
use Storable qw(nstore retrieve);

BEGIN {
    use File::Basename;
    my $path = dirname($0);
    $path = '.' unless defined $path && $path ne '';
    #print STDERR "pushing INC path: [$path]\n";
    push @INC, $path;
}

### user modules
use CPStats;
use Statistics;
use CommandLine;
use SimpleError;
use DataAccess qw(:constants);

### options definition
my %options = (
    'input'          => +{ option => 'input|i=s'             , value =>    undef, check => 1 },
    'output'         => +{ option => 'output|o=s'            , value =>    undef, },
    'storable'       => +{ option => 'storable|s=s'          , value =>    undef, },
    #--
    'repository'     => +{ option => 'repository|r=s'        , value =>    undef, check => 1 },
    'compare-mode'   => +{ option => 'compare-mode|c=i'      , value =>        0, },
    #--
    'percentage'     => +{ option => 'percentage|p'          , value =>        0, },
    'gross-dies'     => +{ option => 'gross-dies|g'          , value =>        0, },
    'FS'             => +{ option => 'field-separator|t=s'   , value =>      ',', },
    #--
    'help'           => +{ option => 'help|h|?'              , value =>    undef, },
    'manual'         => +{ option => 'manual'                , value =>    undef, },
);
use constant OPTION_DEFAULT => '.';
my %option_defaults = (
    'output'         => +{ reference => 'input', value => '*.distrib.csv', base => 1, },
    'storable'       => +{ reference => 'input', value => '*.analysis.dat', base => 1, },
    'FS'             => +{ force => 1, value => sub {
                                                my $sep = shift; $sep =~ s/\.s/ /g;
                                                $sep =~ s/\.t/\t/g; return $sep; },  },
);

### command line interface
pod2usage(-exitstatus => 0, -verbose => 1) if @ARGV == 0;
CommandLine::get_options( \%options, 'config' );
pod2usage(-exitstatus => 0, -verbose => 1) if $options{'help'}{'value'};
pod2usage(-exitstatus => 0, -verbose => 2) if $options{'manual'}{'value'};
CommandLine::apply_checks( \%options );
CommandLine::apply_defaults( \%option_defaults,  \%options, OPTION_DEFAULT );
#message( "options with defaults applied:" );
#CommandLine::print_options( \%options );

### main
my $exec_begin = time();
message("execution begin: ", scalar(localtime($exec_begin)));
my $da = do_input();
my ($output_file, $output_fh);
if (defined($options{output}{value})) {
    $output_file = $options{output}{value};
    open $output_fh, '>', $output_file or error("cannot output to '$output_file': $!");
    select $output_fh;
} else {
    $output_file = '<STDOUT>';
}
my $FS = $options{FS}{value};
my $repo_file = $options{repository}{value};
my $repo = do $repo_file;
error("cannot import repository file: $repo_file: $@.") if $@;
error("invalid repository format.") unless check_repository($repo, $da);
my ($storable_file, $storable_data);
if (defined($options{storable}{value})) {
    $storable_file = $options{storable}{value};
    if (-f $storable_file) {
        $storable_data = retrieve($storable_file)
            or error("cannot retrieve the Storable data from file: $storable_file: $!");
        error("Lot_Number mismatch in the Storable data and the Wafer data!")
            unless ($storable_data->{Lot_Number} eq $da->{Data}{Lot_Number});
        warning("'Stats_Data' section exists, will be overwritten!")
            if ( defined($storable_data->{Stats_Data}) );
    } else {
        message("creating Storable data structure to file: $storable_file");
        $storable_data->{Lot_Number} = $da->{Data}{Lot_Number};
    }
}
my $stats_repo;
my $cpstats    = CPStats::->new();
## data collection
for my $i (0 .. $#$repo) {
    ## initialization
    my $distrib_spec = $repo->[$i];
    my $item      = $distrib_spec->{item};
    my $item_desc = $distrib_spec->{desc};
    my $item_name = (ref($item) eq '') ? $item : defined($item_desc) ? $item_desc : '(calculated data)';
    my $ranges    = $distrib_spec->{ranges};
    my $condition = $distrib_spec->{condition};
    my $mode      = $distrib_spec->{mode};
    ## run the calculations
    message_f("distribution: %-24s (%s)", $item_name, ( defined($condition) ? 'selected dies' : 'all dies' ) );
    my $values    = $da->get_collection($item, $condition);
    my $stats     = $cpstats->set(Comp_Mode => $mode)->cp_stats($values, $ranges);
    my ($distrib_desc, $distrib_count) = @{ $stats->{Distribution} };
    ## verify distribution
    verify_distribution($values, $stats, $da->{Data}{Gross_Dies});
    ## put in repository
    push @$stats_repo, +{Item => $item_name, Desc => $item_desc, Mode => $mode, Stats => $stats};
}

## set the statistics data, and save to disk file if required
$storable_data->{Stats_Data} = $stats_repo;
if (defined($storable_file)) {
    message("storing data '$storable_data' to file '$storable_file'...");
    nstore($storable_data, $storable_file);
    #open DD, '>', 'dump.pl';
    #print DD Data::Dumper::Dumper($storable_data), "\n";
    #close DD;
}

## print the result in ascii text
print_result($da, $storable_data);

## finalize
close $output_fh if ($output_file ne '<STDOUT>');
my $exec_end = time();
message("execution begin: ", scalar(localtime($exec_begin)));
message("execution   end: ", scalar(localtime($exec_end)));
message("time   consumed: ", ($exec_end - $exec_begin), " second(s)");
exit 0;

# =============================================================
# S u b r o u t i n e s
# =============================================================

sub print_result {
    my ($da, $repo) = @_;
    my @out_items;
    my $gross_dies = $da->{Data}{Gross_Dies};
    ## wafer heading
    @out_items = qw(Device_Name Lot_Number Wafer_Number Log_File Gross_Dies CP_Start CP_End);
    print join($FS, @out_items), "\n";
    print join($FS, map $da->{Data}{$_}, @out_items), "\n\n";
    ## traversal of all the stats in the repository
    for my $i (0 .. $#{$repo->{Stats_Data}}) {
        my $stats_item = $repo->{Stats_Data}[$i];
        my $stats      = $stats_item->{Stats};
        my ($distrib_desc, $distrib_count) = @{ $stats->{Distribution} };
        ## specific item's heading
        @out_items = ( $i+1, $stats_item->{Item},
            ( $stats->{Sample_Dies} < $gross_dies ? '(cond.' : '(all' ) . "/$stats_item->{Mode})", );
        push @out_items, $stats_item->{Desc}
            if (defined($stats_item->{Desc}) && $stats_item->{Desc} ne $stats_item->{Item});
        print join($FS, @out_items), "\n";
        ## statistical values (median, mean and stdev)
        @out_items = qw(Gross Samples Valid Median Mean StDev);
        print join($FS, @out_items), "\n";
        my ($median, $mean, $stdev) =
            map( $stats->{$_} eq 'n/a' ? 'n/a' : sprintf( "%.3lf", $stats->{$_} ), qw/Median Mean Stdev/ );
        @out_items = ($gross_dies, $stats->{Sample_Dies}, $stats->{Valid_Dies},
            $median, $mean, $stdev);
        print join($FS, @out_items), "\n";
        ## the distribution info
        # table heading
        @out_items = ( '#', "Limit", "Count", );
        push @out_items, '%' if ($options{percentage}{value});
        push @out_items, 'Total' if ($options{'gross-dies'}{value});
        print ( join($FS, @out_items), "\n" );
        # data rows
        for my $j (0 .. $#$distrib_desc) {
            @out_items = ($j + 1, $distrib_desc->[$j], $distrib_count->[$j]);
            push @out_items, sprintf("%.2lf%%", 100.0 * $distrib_count->[$j] / $gross_dies)
                if ($options{percentage}{value});
            push @out_items, $gross_dies if ($options{'gross-dies'}{value});
            print ( join($FS, @out_items), "\n" );
        }
        print "\n"; # a blank separating line
    }
    1;
}

sub verify_distribution {
    my ($values, $stats, $gross_dies) = @_;
    my $distrib = $stats->{Distribution}[1];
    my $sum = 0;
    $sum += $_ for (@$distrib);
    # $sum should be equal to number of samples (@$values),
    # which is no more than $gross_dies
    if ($sum != @$values || $sum > $gross_dies) {
        error("verification fail: sum=$sum, gross=$gross_dies, values=" . scalar @$values);
    }
    return 1;
}

sub check_repository {
    my ($repo, $da) = @_;
    return ref $repo eq 'ARRAY';
}

sub do_input {
    my $da = DataAccess::->new(Mode => DataAccess::DA_OVERWRITE); # always enable OVERWRITING!
    $da->retrieve($options{input}{value});
    return $da;
}

__END__

=head1 NAME

=over 2

gendistrib

=back

=head1 SYNOPSIS

Generates distribution data.

=head1 Common Usage Scenarios

=over 2

=back

=head1 OPTIONS

=over 2

=item *

-i, --input=FILE
Specify input.

=item *

-o, --output=FILE
Specify output.

=item *

-t, --separator=STRING
Specify separating string.

=back

=head1 DESCRIPTION

=cut
